<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>代理模式</title>
</head>
<body>
<script>
    /**
     * 代理模式就是为对象提供一个代理对象，由此控制对原对象的访问
     * 代理模式有两种形式：保护代理和虚拟代理
     * 保护代理可以控制不同权限的对象对目标对象的访问，一些不满足条件的请求就可直接在代理对象这拒绝掉
     * 虚拟代理可把一些开销大的对象延迟到真正需要它的时候才创建
     * */

    /* 例子，图片预加载
       默认先显示loading，当目标图片加载完成后再替换显示
     */
    const myImage = (function () {
        const imgNode = document.createElement('img')
        const img = new Image()
        document.body.append(imgNode)

        img.onload = function () {
            imgNode.src = img.src
        }

        return {
            setSrc: function (src) {
                imgNode.src = '/loading.gif'
                img.src = src
            }
        }
    }())

    myImage.setSrc('/flower.png')


    /**
     * 以上例子违反了单一职责原则，将设置src和预加载两个功能耦合到了一起
     * 这种耦合会导致脆弱和低内聚的设计
     * 这里可用虚拟代理，将预加载的功能抽离，这样当不需要预加载时
     * 只需要请求本体即可，而不需要深入myImage去修改
     *
     * 需要注意代理和本体的接口要保持一致性，这样用户就可以放心地请求代理
     * 而任何使用本地的地方都可替换成使用代理
     * */
    const myImage = (function () {
        const imgNode = document.createElement('img')
        document.body.append(imgNode)
        return {
            setSrc: function (src) {
                imgNode.src = src
            }
        }
    }())

    const proxyImage = (function () {
        const img = new Image()

        img.onload = function () {
            myImage.setSrc(img.src)
        }
        return {
            setSrc: function (src) {
                myImage.setSrc('/loading.gif')
                img.src = src
            }
        }
    }())


    /**
     * 例子，勾选复选框时，同步文件
     * 使用代理函数收集2秒内需要同步的文件ID，2秒的延迟能大大减轻服务器压力
     * */
    function synchronousFile(id) {
        console.log('开始同步文件，ID为：' + id)
    }

    function proxySynchronousFile() {
        const cache = []
        let timer

        return function (id) {
            cache.push(id)

            if (timer) return

            timer = setTimeout(function () {
                synchronousFile(cache.join(','))
                clearTimeout(timer)
                timer = null
                cache.length = 0
            }, 2000)
        }
    }

    const checkbox = document.getElementsByTagName('input')

    for (let i = 0, c; c = checkbox[i++];) {
        c.onclick = function () {
            if (this.checked === true) {
                proxySynchronousFile(this.id)
            }
        }
    }


    /**
     * 例子，在脚本加载前使用代理对象记录用户操作
     * 脚本加载后再将操作执行
     * */
    const cache = []

    // 代理对象
    // 将用户的每个操作用函数包裹
    let miniConsole = {
        log() {
            const args = arguments
            cache.push(function () {
                return miniConsole.log.apply(miniConsole, args)
            })
        }
    }

    function handler() {
        const script = document.createElement('script')
        script.onload = function () {
            cache.forEach(fn => fn())
        }
        script.src = 'minConsole.js'
        document.body.append(script)
    }

    // 假设点击页面后才开始加载，不考虑重复点击问题，仅是示例
    document.body.addEventListener('click', handler, false)

    // 脚本加载成功后会覆盖代理对象miniConsole
    miniConsole = {
        log() {
            // 真正代码略
        }
    }


    /**
     * 缓存代理可为一些开销大的运算结果提供暂时的存储
     * 下次计算若参数跟之前的一致，则直接返回前面存储的运算结果
     * */
    function multiplication(...args) {
        let sum = 1

        for (let i = 0; i < args.length; i++) {
            sum *= args[i]
        }

        return sum
    }

    function proxyMultiplication() {
        const cache = {}

        return function () {
            const args = Array.prototype.join.call(arguments, ',')

            if (args in cache) {
                return cache[args]
            }

            return cache[args] = multiplication.apply(this, arguments)
        }
    }

    proxyMultiplication(1, 2, 3)
    proxyMultiplication(1, 2, 3)
</script>
</body>
</html>